#!/usr/bin/Rscript 

args <- commandArgs(trailingOnly = TRUE)
if(length(args) < 2){
  print('specify start and end date for data processing in form YYYY-MM-DD')
  quit('no', 1, FALSE)
}

start.date <- args[1]
end.date <- args[2]

# open db and import data
library(RMySQL)

con <- dbConnect(MySQL(), dbname="vmstats", user="openstack", pass="openstack")

date.format <- '%Y-%m-%d %H:%i:%S'
my.statement <- sprintf("select vm, host, cpu, date_format(time, '%s') as timestamp from vmstats where time > '%s' and time < '%s'",
                     date.format, start.date, end.date)

vmstats <- dbGetQuery(con, statement = my.statement)

if(length(vmstats) == 0) {
  quit('no', 0, FALSE)
} 

vmstats$timestamp <- with (vmstats, as.POSIXct(strptime(timestamp, format="%Y-%m-%d %H:%M:%S")) )

# get number of hosts
all.hosts <- unique(unlist(vmstats["host"]))

# get vm names
all.vms <- unique(unlist(vmstats['vm']))

# Re-arrangement is done one by one. We check what host has bigger peak loads 
# p = (sum(a) + sqrt( (sum(b))^2 +(sum(c))2 )
# Then we take all VMs of the host and try to find better home for them - calculating possible peaks

# vmloads

decompose.vmloads <- function (p.vmstats) {
  # get vm names
  vms <- unique(unlist(p.vmstats['vm']))
  a <- b <- c <- rep(0, length(vms))
  vmloads <- data.frame(a, b, c,stringsAsFactors = FALSE)
  rownames(vmloads) <- vms
  # get load per vm in a period
  for(vm in vms) {
    vm.stat <- vmstats[p.vmstats$vm == vm, 
                       c('timestamp', 'cpu')]
    # smoothing
    vm.stat$cpu <-filter(vm.stat$cpu, rep(1/60,60), side=1, circular=TRUE)
    # decomposition of time series
    TIME <- unclass(vm.stat$timestamp)
    COS <- cos(2 * pi * TIME / (24 * 60 * 60))
    SIN <- sin(2 * pi * TIME / (24 * 60 * 60))
    vmstat.lm <- lm(vm.stat$cpu ~ COS + SIN )
    a <- coef(vmstat.lm)[1]
    b <- coef(vmstat.lm)[2]
    c <- coef(vmstat.lm)[3]
    ampl <- sqrt(b^2 + c^2)
    # move sin() up if amplitude > mean
    if(a < ampl) a = ampl;  
    vmloads[vm, 'a'] <- a
    vmloads[vm, 'b'] <- b
    vmloads[vm, 'c'] <- c
  }
  return (vmloads)
}

vms.coeffs <- decompose.vmloads(vmstats)

# hosts peaks, calculated from vm coefficients
# p = (sum(a) + sqrt( (sum(b))^2 +(sum(c)) ^ 2) )
# host.vm is a table of hosts and corresponding vms

decompose.host.loads <- function (p.vms.coeffs, p.host.vms) {
  # get host names
  hosts <- unique(unlist(p.host.vms$host))
  a <- b <- c <- p <- rep(0, length(hosts))
  hosts.coeffs <- data.frame(a, b, c, p, stringsAsFactors = FALSE)
  rownames(hosts.coeffs) <- hosts
  for(host in hosts) {
    vms <- p.host.vms[p.host.vms$host == host,'vm']
    host.vms.coeffs <- p.vms.coeffs[vms,]
    hosts.coeffs[host, c('a', 'b', 'c')] <- c(sum(host.vms.coeffs$a),
                                              sum(host.vms.coeffs$b),
                                              sum(host.vms.coeffs$c))
    hosts.coeffs[host, 'p'] = hosts.coeffs[host, 'a'] + 
      sqrt(hosts.coeffs[host, 'b'] ^2 + hosts.coeffs[host, 'c'] ^2)
  }
  return (hosts.coeffs)
}

hosts.vms <- unique(vmstats[c('host', 'vm')])
hosts.coeffs <- decompose.host.loads ( vms.coeffs, hosts.vms)
hosts.to.optimize <- all.hosts

## repeat for all hosts ##

# if in hosts to optimize only one is left - we are finished
while (length(hosts.to.optimize) > 1 ) {
  
  ### overloaded host
  hosts.coeffs.optimize <- hosts.coeffs[hosts.to.optimize,]
  overloaded.ind <- which (hosts.coeffs.optimize$p == 
                             max(hosts.coeffs.optimize$p),
                           arr.ind=TRUE)
  overloaded.host <- rownames(hosts.coeffs.optimize)[overloaded.ind]
  
  # VMs to move
  vms.to.move <- hosts.vms[hosts.vms$host == overloaded.host, 'vm']
  # minimize max peak
  # start with the first vm
  vm.to.move <- NA
  vm.new.host <- NA
  max.host.load <- max(hosts.coeffs.optimize$p)
  for(vm in vms.to.move) {
    # if max load among hosts after vm move is less, replace vm.to.move
    # new load coeffs of host = old load coeffs + vm load coeffs
    other.hosts <- hosts.to.optimize[hosts.to.optimize != overloaded.host] 
    for(host in other.hosts) {
      new.load <- hosts.coeffs[host, 'a'] + vms.coeffs[vm, 'a'] +
        sqrt( (hosts.coeffs[host, 'b'] + vms.coeffs[vm, 'b']) ^ 2 +
                (hosts.coeffs[host, 'c'] + vms.coeffs[vm, 'c']) ^ 2 )
      if(new.load < max.host.load) {
        max.host.load <- new.load
        vm.to.move <- vm
        vm.new.host <- host
      }
    }
  }
  
  vm.old.host <- overloaded.host
  
  # if we have no vm.to.move we cannot optimize further host with max load
  if(is.na(vm.to.move )) {
    # remove host from optimiation attempts  
    hosts.to.optimize <- hosts.to.optimize[hosts.to.optimize != overloaded.host]
  } else {
    
    # change hosts.coeffs, hosts.vms  
    vm.a <- vms.coeffs[vm.to.move, 'a']
    vm.b <- vms.coeffs[vm.to.move, 'b']
    vm.c <- vms.coeffs[vm.to.move, 'c']
    
    hosts.coeffs[vm.new.host, 'a'] <- hosts.coeffs[vm.new.host, 'a'] + vm.a
    hosts.coeffs[vm.new.host, 'b'] <- hosts.coeffs[vm.new.host, 'b'] + vm.b
    hosts.coeffs[vm.new.host, 'c'] <- hosts.coeffs[vm.new.host, 'c'] + vm.c
    hosts.coeffs[vm.new.host, 'p'] <- hosts.coeffs[vm.new.host, 'a'] + 
      sqrt( hosts.coeffs[vm.new.host, 'b'] ^ 2 +
              hosts.coeffs[vm.new.host, 'c'] ^ 2 )
    
    hosts.coeffs[vm.old.host, 'a'] <- hosts.coeffs[vm.old.host, 'a'] - vm.a
    hosts.coeffs[vm.old.host, 'b'] <- hosts.coeffs[vm.old.host, 'b'] - vm.b
    hosts.coeffs[vm.old.host, 'c'] <- hosts.coeffs[vm.old.host, 'c'] - vm.c
    hosts.coeffs[vm.old.host, 'p'] <- hosts.coeffs[vm.old.host, 'a'] + 
      sqrt( hosts.coeffs[vm.old.host, 'b'] ^ 2 +
              hosts.coeffs[vm.old.host, 'c'] ^ 2 )
    
    hosts.vms[hosts.vms$vm == vm.to.move, 'host'] <- vm.new.host
    cat(vm.to.move, vm.new.host, '\n')
  }
}
